#ifndef ___fat32_lista_bloques_para_escribir

#define ___fat32_lista_bloques_para_escribir 1

#include "f32_base.c"
#include "operbits.c"
#include <stdlib.h>
#include <stdarg.h>

struct fat32_bloque_para_escribir {
  char datos[2048];
  char mascara[256];
  unsigned long numero;
};

typedef struct {
  unsigned int cantidadBloques;
  fat32_bloque_para_escribir * bloques;
} fat32_lista_bloques_para_escribir;

enum fat32_operaciones_lista_bloques_para_escribir {
  FAT32_LBPE_CREAR = 1,
  FAT32_LBPE_ESCRIBIR = 2,
  FAT32_LBPE_DESTRUIR = 3,
  FAT32_LBPE_SIGUIENTE_BLOQUE = 4,
  FAT32_LBPE_REMOVER_BLOQUE = 5,
  FAT32_LBPE_DATOS_BLOQUE = 6,
  FAT32_LBPE_MASCARA_BLOQUE = 7,
  FAT32_LBPE_MASCARA_SECTORES_BLOQUE = 8,
  FAT32_LBPE_BLOQUE_EXISTE_EN_LISTA = 9,
  FAT32_LBPE_CANTIDAD_BLOQUES = 10
};

void fat32_operar_sobre_lista_bloques_para_escribir(unsigned char, fat32_lista_bloques_para_escribir *, unsigned long, unsigned int,
                                                                                                               unsigned int, void *);
void fat32_combinar_listas_bloques_para_escribir(fat32_lista_bloques_para_escribir *, ...);

unsigned int fat32_buscar_bloque_lbpe(fat32_lista_bloques_para_escribir *, unsigned long);
unsigned int fat32_crear_bloque_lbpe(fat32_lista_bloques_para_escribir *, unsigned long);
void fat32_actualizar_bloque_lbpe(fat32_lista_bloques_para_escribir *, unsigned int, unsigned int, unsigned int, void *);
void fat32_aplicar_lista_bloques_para_escribir(fat32_lista_bloques_para_escribir *, fat32_lista_bloques_para_escribir *);

void fat32_operar_sobre_lista_bloques_para_escribir (unsigned char operacion, fat32_lista_bloques_para_escribir * lbpe,
                                                     unsigned long bloque, unsigned int inicio, unsigned int cantidadDatos, void * datos) {
  unsigned int posicion;
  switch (operacion) {
  case FAT32_LBPE_CREAR:
    lbpe -> cantidadBloques = 0;
    lbpe -> bloques = NULL;
    break;
  case FAT32_LBPE_ESCRIBIR:
    posicion = fat32_buscar_bloque_lbpe(lbpe, bloque);
    if (!posicion)
      posicion = fat32_crear_bloque_lbpe(lbpe, bloque);
    else
      posicion --;
    fat32_actualizar_bloque_lbpe(lbpe, posicion, inicio, cantidadDatos, datos);
  case FAT32_LBPE_DESTRUIR:
    if (lbpe -> bloques) free(lbpe -> bloques);
    break;
  case FAT32_LBPE_SIGUIENTE_BLOQUE:
    if (!(lbpe -> cantidadBloques)) break;
    *((unsigned long *) datos) = (lbpe -> bloques) -> numero;
    break;
  case FAT32_LBPE_REMOVER_BLOQUE:
    posicion = fat32_buscar_bloque_lbpe(lbpe, bloque);
    if (!posicion) break;    
    posicion --;
    (lbpe -> cantidadBloques) --;
    if (posicion != (lbpe -> cantidadBloques)) (lbpe -> bloques)[posicion] = (lbpe -> bloques)[lbpe -> cantidadBloques];
    lbpe -> bloques = fb_cambiar_tamano(lbpe -> bloques, sizeof (struct fat32_bloque_para_escribir), lbpe -> cantidadBloques);
    break;
  case FAT32_LBPE_DATOS_BLOQUE:
    posicion = fat32_buscar_bloque_lbpe(lbpe, bloque);
    if (!posicion) break;    
    posicion --;
    memcpy(datos, ((lbpe -> bloques)[posicion]).datos, 2048);
    break;
  case FAT32_LBPE_MASCARA_BLOQUE:
    posicion = fat32_buscar_bloque_lbpe(lbpe, bloque);
    if (!posicion) break;    
    posicion --;
    memcpy(datos, ((lbpe -> bloques)[posicion]).mascara, 256);
    break;
  case FAT32_LBPE_MASCARA_SECTORES_BLOQUE:
    posicion = fat32_buscar_bloque_lbpe(lbpe, bloque);
    if (!posicion) break;    
    posicion --;
    char sectores = 0;
    unsigned short pos, sec;
    for (sec = 0; sec < 4; sec ++)
      for (pos = 0; pos < 64; pos ++)
        if ((((lbpe -> bloques)[posicion]).mascara)[pos]) sectores |= 1 << sec;
    *((char *) datos) = sectores;
    break;
  case FAT32_LBPE_BLOQUE_EXISTE_EN_LISTA:
    *((char *) datos) = fat32_buscar_bloque_lbpe(lbpe, bloque) ? 1 : 0;
    break;
  case FAT32_LBPE_CANTIDAD_BLOQUES:
    *((unsigned long *) datos) = lbpe -> cantidadBloques;
  }
}

void fat32_combinar_listas_bloques_para_escribir (fat32_lista_bloques_para_escribir * resultado, ...) {
  va_list listas;
  fat32_operar_sobre_lista_bloques_para_escribir(FAT32_LBPE_CREAR, resultado, 0, 0, 0, NULL);
  va_start(listas, resultado);
  fat32_lista_bloques_para_escribir * elemento = va_arg(listas, fat32_lista_bloques_para_escribir *);
  while (elemento) {
    fat32_aplicar_lista_bloques_para_escribir(listas, elemento);
    elemento = va_arg(resultado, fat32_lista_bloques_para_escribir *);
  }
  va_end(listas);
}

unsigned int fat32_buscar_bloque_lbpe (fat32_lista_bloques_para_escribir * lbpe, unsigned long bloque) {
  unsigned int posicion;
  for (posicion = 0; posicion < (lbpe -> cantidadBloques); posicion ++)
    if (bloque == ((lbpe -> bloques)[posicion].numero))
      return posicion + 1;
  return 0;
}

unsigned int fat32_crear_bloque_lbpe (fat32_lista_bloques_para_escribir * lbpe, unsigned long bloque) {
  unsigned int posicion;
  for (posicion = 0; posicion < (lbpe -> cantidadBloques); posicion ++)
    if (!((lbpe -> bloques)[posicion].numero)) break;
  if (posicion == (lbpe -> cantidadBloques)) {
    (lbpe -> cantidadBloques) ++;
    lbpe -> bloques = fb_cambiar_tamano(lbpe -> bloques, sizeof (struct fat32_bloque_para_escribir), lbpe -> cantidadBloques);
  }
  ((lbpe -> bloques)[posicion]).numero = bloque;
  unsigned short p;
  for (p = 0; p < 2048; p ++) {
    (((lbpe -> bloques)[posicion]).datos)[p] = 0;
    if (p < 256) (((lbpe -> bloques)[posicion]).mascara)[p] = 0;
  }
  return posicion;
}

void fat32_actualizar_bloque_lbpe (fat32_lista_bloques_para_escribir * lbpe, unsigned int posicionBloque, unsigned int inicio, 
                                                                                          unsigned int cantidad, void * datos) {
  char mascaraInterna[256];
  unsigned short posicion;
  for (posicion = 0; posicion < 256; posicion ++) mascaraInterna[posicion] = 0;
  for (posicion = inicio; posicion < (inicio + cantidad); posicion ++)
    mascaraInterna[posicion / 8] |= 1 << (posicion % 8);
  memcpy(((lbpe -> bloques)[posicionBloque]).datos + inicio, datos, cantidad);
  for (posicion = 0; posicion < 256; posicion ++)
    ((lbpe -> bloques)[posicionBloque).mascara[posicion] |= mascaraInterna[posicion];
}

void fat32_aplicar_lista_bloques_para_escribir (fat32_lista_bloques_para_escribir * destino, fat32_lista_bloques_para_escribir * origen) {
  unsigned long bloque;
  unsigned long posicion;
  unsigned long posicionD;
  char mascara1[256];
  char mascara2[256];
  char datos1[2048];
  char datos2[2048];
  unsigned short posicionB;
  for (posicion = 0; posicion < (origen -> cantidadBloques); posicion ++) {
    bloque = ((origen -> bloques)[posicion]).numero;
    memcpy(mascara1, ((origen -> bloques)[posicion]).mascara, 256);
    memcpy(datos1, ((origen -> bloques)[posicion]).datos, 2048);
    posicionD = fat32_buscar_bloque_lbpe(destino, bloque);
    if (!posicionD) {
      posicionD = fat32_crear_bloque_lbpe(destino, bloque);
      memcpy(((destino -> bloques)[posicionD]).mascara, mascara1, 256);
      memcpy(((destino -> bloques)[posicionD]).datos, datos1, 2048);
      continue;
    }
    posicionD --;
    memcpy(mascara2, ((destino -> bloques)[posicionD]).mascara, 256);
    memcpy(datos2, ((destino -> bloques)[posicionD]).datos, 2048);
    for (posicionB = 0; posicionB < 2048; posicionB ++)
      if (obtenerBits(mascara1[posicionB / 8], posicionB % 8, 1)) {
        mascara2[posicionB / 8] |= 1 << (posicionB % 8);
        datos2[posicionB] = datos1[posicionB];
      }
    memcpy(((destino -> bloques)[posicionD]).mascara, mascara2, 256);
    memcpy(((destino -> bloques)[posicionD]).datos, datos2, 2048);
  }
}

#endif